﻿// ***********************************************************************
// Assembly         : Tx.OCC
// Author           : tianteng
// Created          : 03-09-2021
//
// Last Modified By : tianteng
// Last Modified On : 01-26-2021
// ***********************************************************************
// <copyright file="SliceByPlanes.cs" company="Tobias Schachte">
//     Copyright © 2015-2021 Tobias Schachte
// </copyright>
// <summary></summary>
// ***********************************************************************
using Occt;
using System;

namespace Tx.OCC
{
    /// <summary>
    /// Class SliceByPlanes.
    /// </summary>
    public class SliceByPlanes
    {
        #region Structs

        /// <summary>
        /// Struct Slice
        /// </summary>
        public struct Slice
        {
            /// <summary>
            /// Gets the b rep.
            /// </summary>
            /// <value>The b rep.</value>
            public TopoDS_Shape BRep { get; }
            /// <summary>
            /// Gets the cut plane.
            /// </summary>
            /// <value>The cut plane.</value>
            public gp_Pln CutPlane { get; }

            /// <summary>
            /// Initializes a new instance of the <see cref="Slice"/> struct.
            /// </summary>
            /// <param name="brep">The brep.</param>
            /// <param name="cutPlane">The cut plane.</param>
            public Slice(TopoDS_Shape brep, gp_Pln cutPlane)
            {
                BRep = brep;
                CutPlane = cutPlane;
            }
        }



        #endregion

        #region Properties

        /// <summary>
        /// Gets the source shape.
        /// </summary>
        /// <value>The source shape.</value>
        public TopoDS_Shape SourceShape { get; }
        /// <summary>
        /// Gets the reference face.
        /// </summary>
        /// <value>The reference face.</value>
        public TopoDS_Face ReferenceFace { get; }
        /// <summary>
        /// Gets the slice count.
        /// </summary>
        /// <value>The slice count.</value>
        public int SliceCount { get; }
        /// <summary>
        /// Gets the slice direction.
        /// </summary>
        /// <value>The slice direction.</value>
        public gp_Dir SliceDirection { get; private set; }
        /// <summary>
        /// Gets the slice thickness.
        /// </summary>
        /// <value>The slice thickness.</value>
        public double SliceThickness { get; private set; }

        /// <summary>
        /// Gets the slices.
        /// </summary>
        /// <value>The slices.</value>
        public Slice[] Slices
        {
            get
            {
                return _Successful ? _Slices : null;
            }
        }



        #endregion

        #region Create

        /// <summary>
        /// Initializes a new instance of the <see cref="SliceByPlanes"/> class.
        /// </summary>
        /// <param name="sourceShape">The source shape.</param>
        /// <param name="refFace">The reference face.</param>
        /// <param name="sliceCount">The slice count.</param>
        public SliceByPlanes(TopoDS_Shape sourceShape, TopoDS_Face refFace, int sliceCount)
        {
            SourceShape = sourceShape;
            SliceCount = sliceCount;
            ReferenceFace = refFace;
            _Slices = new Slice[sliceCount];
        }



        #endregion

        #region Make

        /// <summary>
        /// The slices
        /// </summary>
        readonly Slice[] _Slices;
        /// <summary>
        /// The reference plane
        /// </summary>
        gp_Pln _RefPlane;
        /// <summary>
        /// The debug output
        /// </summary>
        bool _DebugOutput;
        /// <summary>
        /// The successful
        /// </summary>
        bool _Successful;



        /// <summary>
        /// Creates the slices.
        /// </summary>
        /// <param name="debugOutput">if set to <c>true</c> [debug output].</param>
        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
        public bool CreateSlices(bool debugOutput, Action<string> messageError, Action<string> messageTrace)
        {
            _Successful = false;
            _DebugOutput = debugOutput;

            if (!(_FindParameters(messageError, messageTrace)
                  && _CreateSlices(messageError)))
            {
                return false;
            }

            _Successful = true;
            return true;
        }



        /// <summary>
        /// Finds the parameters.
        /// </summary>
        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
        bool _FindParameters(Action<string> messageError, Action<string> messageTrace)
        {
            if (ReferenceFace == null)
            {
                messageError?.Invoke("Reference face is not valid.");
                return false;
            }

            // Get Opface
            var opFace = OccFaceEx.FindOppositeFace(SourceShape, ReferenceFace, farthest: true, OccFaceEx.FaceDistanceMethod.UseFacePlane);
            if (opFace == null)
            {
                messageError?.Invoke("Reference face has no opposite face, so it seems that the shape has no thickness.");
                return false;
            }

            if (_DebugOutput)
            {
                messageTrace?.Invoke($"Reference face index {SourceShape.Faces().IndexOf(ReferenceFace)}, " +
                             $"opposite face has index {SourceShape.Faces().IndexOf(opFace)}, " +
                             $"thickness is {SliceThickness}");
            }

            // Calc cutting plane
            if (!(OccFaceEx.GetCenteredPlaneFromFace(ReferenceFace, out var cutPlane, messageError)
                && OccFaceEx.GetCenteredPlaneFromFace(opFace, out var opPlane, messageError)))
            {
                messageError?.Invoke("Cannot create cutting plane from reference or opposite face.");
                return false;
            }

            SliceThickness = cutPlane.Distance(opPlane);
            SliceDirection = cutPlane.Axis.Direction.Reversed;
            _RefPlane = new gp_Pln(new gp_Ax3(cutPlane.Location, SliceDirection));

            return true;
        }



        /// <summary>
        /// Creates the slices.
        /// </summary>
        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
        bool _CreateSlices(Action<string> messageError)
        {
            var sliceInterval = SliceThickness / SliceCount;
            for (int sliceIndex = 0; sliceIndex < SliceCount; sliceIndex++)
            {
                var sliceOffset = sliceInterval * (sliceIndex + 0.5);
                var cutPlane = _RefPlane.Translated(SliceDirection.ToVec().Multiplied(sliceOffset));
                new BRep_Builder().MakeFace(out TopoDS_Face cutPlaneFace, new Geom_Plane(cutPlane), 1e-7);

                // Create contour
                var common = new BRepAlgoAPI_Common(SourceShape, cutPlaneFace);
                if (!common.IsDone)
                {
                    messageError?.Invoke("Cannot create contour face from shape.");
                    return false;
                }
                var bodySpaceShape = common.Shape;

                // Move to origin
                var transform = new gp_Trsf();
                transform.SetTransformation(gp_Ax3.XOY, cutPlane.Position);
                var transformer = new BRepBuilderAPI_Transform(bodySpaceShape, transform, true);
                var shape = transformer.Shape;

                var slice = new Slice(shape, cutPlane);

                _Slices[sliceIndex] = slice;
            }

            return true;
        }



        /// <summary>
        /// Reconstructs this instance.
        /// </summary>
        /// <returns>TopoDS_Compound.</returns>
        public TopoDS_Compound Reconstruct(Action<string> messageError)
        {
            var builder = new BRep_Builder();
            builder.MakeCompound(out TopoDS_Compound compound);

            var thicknessVector = SliceDirection.ToVec().Multiplied(SliceThickness / Slices.Length);
            if (thicknessVector.SquareMagnitude == 0)
            {
                messageError?.Invoke("Sliced shape has no thickness.");
                return compound;
            }

            for (int index = 0; index < Slices.Length; index++)
            {
                var basePlane = Slices[index].CutPlane.Translated(thicknessVector.Multiplied(0.5).ToPnt(), gp_Pnt.Origin);
                var trsf = new gp_Trsf();
                trsf.SetTransformation(basePlane.Position, gp_Ax3.XOY);
                var location = new TopLoc_Location(trsf);
                var relocatedShape = Slices[index].BRep.Located(location);
                var thickener = new BRepPrimAPI_MakePrism(relocatedShape, thicknessVector, true);
                builder.Add(compound, thickener.Shape);
            }

            return compound;
        }



        #endregion
    }
}